package optional

import (
	"errors"
	"reflect"
)

type Optional[T any] struct {
	value T
	err   error
}

func Of[T any](value T, err error) *Optional[T] {
	return &Optional[T]{
		value: value,
		err:   err,
	}
}

func ofNull[T any](err error) *Optional[T] {
	return &Optional[T]{err: err}
}

func (o *Optional[T]) Get() (T, error) {
	if o.err != nil {
		return o.value, errors.New("no value present. Because " + o.err.Error())
	}
	return o.value, nil
}

func (o *Optional[T]) IsPresent() bool {
	return o.err == nil
}

func (o *Optional[T]) IfPresent(f func(T)) {
	if o.err == nil {
		f(o.value)
	}
}

func (o *Optional[T]) Filter(f func(T) bool) *Optional[T] {
	if o.err != nil {
		return o
	}
	if !f(o.value) {
		o.err = errors.New("filter returns false")
	}
	return o
}

func (o *Optional[T]) OrElse(other T) T {
	if o.err != nil {
		return other
	}
	return o.value
}

func (o *Optional[T]) OrElseGet(f func() T) T {
	if o.err != nil {
		return f()
	}
	return o.value
}

func (o *Optional[T]) Equals(v interface{}) bool {
	t, ok := v.(Optional[T])
	if !ok {
		return false
	}
	return reflect.DeepEqual(o.value, t.value)
}

func (o *Optional[T]) MapToInt8(f func(T) int8) *Optional[int8] {
	return Map(o, f)
}

func (o *Optional[T]) MapToInt(f func(T) int) *Optional[int] {
	return Map(o, f)
}

func (o *Optional[T]) MapToInt16(f func(T) int16) *Optional[int16] {
	return Map(o, f)
}

func (o *Optional[T]) MapToInt32(f func(T) int32) *Optional[int32] {
	return Map(o, f)
}

func (o *Optional[T]) MapToInt64(f func(T) int64) *Optional[int64] {
	return Map(o, f)
}

func (o *Optional[T]) MapToUint8(f func(T) uint8) *Optional[uint8] {
	return Map(o, f)
}

func (o *Optional[T]) MapToUint16(f func(T) uint16) *Optional[uint16] {
	return Map(o, f)
}

func (o *Optional[T]) MapToUint32(f func(T) uint32) *Optional[uint32] {
	return Map(o, f)
}

func (o *Optional[T]) MapToUint64(f func(T) uint64) *Optional[uint64] {
	return Map(o, f)
}

func (o *Optional[T]) MapToUint(f func(T) uint) *Optional[uint] {
	return Map(o, f)
}

func (o *Optional[T]) MapToBool(f func(T) bool) *Optional[bool] {
	return Map(o, f)
}

func (o *Optional[T]) MapToFloat32(f func(T) float32) *Optional[float32] {
	return Map(o, f)
}

func (o *Optional[T]) MapToFloat64(f func(T) float64) *Optional[float64] {
	return Map(o, f)
}

func (o *Optional[T]) MapToComplex64(f func(T) complex64) *Optional[complex64] {
	return Map(o, f)
}

func (o *Optional[T]) MapToComplex128(f func(T) complex128) *Optional[complex128] {
	return Map(o, f)
}

func (o *Optional[T]) MapToAny(f func(T) any) *Optional[any] {
	return Map(o, f)
}

func (o *Optional[T]) FlatMapToInt8(f func(T) *Optional[int8]) *Optional[int8] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToInt(f func(T) *Optional[int]) *Optional[int] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToInt16(f func(T) *Optional[int16]) *Optional[int16] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToInt32(f func(T) *Optional[int32]) *Optional[int32] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToInt64(f func(T) *Optional[int64]) *Optional[int64] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToUint8(f func(T) *Optional[uint8]) *Optional[uint8] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToUint16(f func(T) *Optional[uint16]) *Optional[uint16] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToUint32(f func(T) *Optional[uint32]) *Optional[uint32] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToUint64(f func(T) *Optional[uint64]) *Optional[uint64] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToUint(f func(T) *Optional[uint]) *Optional[uint] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToBool(f func(T) *Optional[bool]) *Optional[bool] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToFloat32(f func(T) *Optional[float32]) *Optional[float32] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToFloat64(f func(T) *Optional[float64]) *Optional[float64] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToComplex64(f func(T) *Optional[complex64]) *Optional[complex64] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToComplex128(f func(T) *Optional[complex128]) *Optional[complex128] {
	return FlatMap(o, f)
}

func (o *Optional[T]) FlatMapToAny(f func(T) *Optional[any]) *Optional[any] {
	return FlatMap(o, f)
}

func Map[T any, V any](o *Optional[T], f func(T) V) *Optional[V] {
	if !o.IsPresent() {
		return ofNull[V](o.err)
	}
	return Of(f(o.value), o.err)
}

func FlatMap[T any, V any](o *Optional[T], f func(T) *Optional[V]) *Optional[V] {
	if !o.IsPresent() {
		return ofNull[V](o.err)
	}
	return f(o.value)
}
